package util;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Properties;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import dao.AccessMySQL;
import dao.DBException;
import dao.DBTypeEnum;
import dao.DSN;
import db.HostName;
import helper.DSNHelper;
/**
 * Panel to get and check database settings.
 * Can be used as dialog for user inputs.
 * @author Jolle
 * @version 1.0.0
 */
public class DBConfig extends JFrame {
	
	private static final long serialVersionUID = 8568594399892096637L;
	/**
	 * name of a configuration file (in project directory) 
	 */
	private static final String INI_FILE_NAME = "config.ini";
	/**
	 * name of the database folder (in project directory) 
	 */
	private static final String DB_DIRECTORY  = "database";
	/**
	 * message displayed in combo box if MySQL is used
	 */
	private final String CONNECT = "(press \"Connect\")";
	
	private DBConfig dbConfig = this;
	private DBTypeEnum connType = DBTypeEnum.NONE;
	private DSN dsn = null;
	private Properties configProps = new Properties();
	
	private JComboBox<HostName> cboHost;
	private JTextField txtPort;
	private JTextField txtUser;
	private JTextField txtPass;
	private JButton btnConnect;
	private JComboBox<String> cboDatabase;
	
	/**
	 * Constructor
	 */
	public DBConfig() {
		super();
	}
	/**
	 * Read configuration from INI file and call displayDialog()
	 * if any value is missing.
	 * @throws DBException
	 */
	public void readConfiguration() throws DBException {
		String iniFullPath = Config.APP_DIR + INI_FILE_NAME;
		Debug.print("configuration file: " + iniFullPath);
		boolean showConfigDialog = false;
		if (Config.SHOW_DB_CONFIG_DIALOG) {
			showConfigDialog = true;
		}
		/* check INI file */
		FileAccess fileAccess = new FileAccess();
		if (fileAccess.fileExists(iniFullPath)) {
			this.configProps = fileAccess.readPropertiesFromIni(iniFullPath);
			if (!this.isConfigOK(false)) {
				showConfigDialog = true;
			}
		}
		else {
			showConfigDialog = true;
		}
		/* add connection type */
		this.configProps.setProperty("type", getConnTypeFromHost(
				this.configProps.getProperty("host")).toString());
		/* add db path, if it doesn't exist */
		if (!this.configProps.containsKey("dbpath")) {
			this.configProps.setProperty("dbpath", 
					Config.APP_DIR + DB_DIRECTORY + "\\");
		}
		Debug.print("read configuration:\n" + 
				DSNHelper.getPropertiesInfo(this.configProps));
		if (showConfigDialog) {
			/* display configuration dialog */
			this.displayDialog("Connection settings");
			Config.SHOW_DB_CONFIG_DIALOG = false;
		}
	}
	/**
	 * Set the configuration.
	 * @param configProps
	 */
	public void setConfiguration(Properties configProps) {
		this.configProps = configProps;
	}
	/**
	 * Get the configuration.
	 * @return configProps
	 */
	public Properties getConfiguration() {
		return this.configProps;
	}
	/**
	 * Set the DSN.
	 * @param dsn
	 */
	public void setDSN(DSN dsn) {
		this.dsn = dsn;
	}
	/**
	 * Get the DSN.
	 * @return
	 */
	public DSN getDSN() {
		if (this.dsn == null) {
			this.dsn = DSNHelper.getDSNfromProperties(this.configProps);
		}
		return this.dsn;
	}
	/**
	 * Check whether necessary values for DSN and database are available.
	 * @param showMsg: if true, message boxes will appear if values are missing
	 * @return true, if configuration is complete
	 */
	public boolean isConfigOK(boolean showMsg) {
		boolean configIsOK = true;
		String message = "";
		JComponent control = null;
		if (this.configProps.getProperty("host").equals("")) {
			message = "Please enter a host";
			control = this.cboHost;
			configIsOK = false;
		}
		else if (this.configProps.getProperty("port").equals("")) {
			message = "Please enter a port";
			control = this.txtPort;
			configIsOK = false;
		}
		/*
		else if (this.configProps.getProperty("user").equals("")) {
			message = "Please enter a user";
			control = this.txtUser;
			configIsOK = false;
		}
		else if (this.configProps.getProperty("pass").equals("")) {
			message = "Please enter a password";
			control = this.txtPass;
			configIsOK = false;
		}
		else if (this.configProps.getProperty("dbname").equals("")) {
			message = "Please enter a database";
			control = this.cboDatabase;
			configIsOK = false;
		}
		//*/
		if (showMsg && !configIsOK) {
			JOptionPane.showMessageDialog(null, message,
					"Missing input", JOptionPane.WARNING_MESSAGE);
			control.requestFocusInWindow();
		}
		return configIsOK;
	}
	/**
	 * Build GUI for configuration dialog and display it.
	 * @param title
	 * @throws DBException
	 */
	public void displayDialog(String title) throws DBException {
		this.setTitle(title);
		this.setLayout(new GridBagLayout());
		this.setMinimumSize(new Dimension(300, 300));
		this.setLocation((Toolkit.getDefaultToolkit().getScreenSize().width  / 2) - (this.getWidth()  / 2), 
				 		 (Toolkit.getDefaultToolkit().getScreenSize().height / 2) - (this.getHeight() / 2));
		this.setResizable(false);
		
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets = new Insets(5, 10, 5, 10);	// T,L,B,R
		int row = 0;
		
		gbc.gridx = 0;
		gbc.gridy = row;
	    gbc.gridwidth = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.weightx = 0.4;
		gbc.weighty = 0;
		this.add(new JLabel("Host"), gbc);
		
		this.cboHost = new JComboBox<HostName>();
		this.cboHost.setEditable(true);
		this.cboHost.addItem(new HostName(DBTypeEnum.ACCESS, "Access"));
		this.cboHost.addItem(new HostName(DBTypeEnum.DERBY, "Derby"));
		this.cboHost.addItem(new HostName(DBTypeEnum.MYSQL, "localhost"));
		this.cboHost.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				dbConfig.connType = dbConfig.getConnTypeFromHost(dbConfig.getHostName());
				dbConfig.handleHostInput();
				dbConfig.cboDatabase.removeAllItems();
				if (dbConfig.connType == DBTypeEnum.MYSQL) {
					// press button "Connect" to list available databases
					dbConfig.btnConnect.setEnabled(true);
					dbConfig.cboDatabase.addItem(dbConfig.CONNECT);
					dbConfig.cboDatabase.setEnabled(false);
				}
				else {
					dbConfig.btnConnect.setEnabled(false);
					dbConfig.cboDatabase.addItem(dbConfig.configProps.getProperty("dbname"));
					dbConfig.cboDatabase.setEnabled(true);
				}
			}
		});
	    gbc.gridx = 1;
	    gbc.gridy = row;
	    gbc.weightx = 0.6;
	    gbc.weighty = 0;
		this.add(this.cboHost, gbc);
		
		row++;
		
		gbc.gridx = 0;
		gbc.gridy = row;
		gbc.weightx = 0.4;
		this.add(new JLabel("Port"), gbc);
		
		this.txtPort = new JTextField();
		gbc.gridx = 1;
		gbc.gridy = row;
	    gbc.weightx = 0.6;
		this.add(this.txtPort, gbc);
		
		row++;
		
		gbc.gridx = 0;
		gbc.gridy = row;
		gbc.weightx = 0.4;
		this.add(new JLabel("User ID"), gbc);
		
		this.txtUser = new JTextField();
		gbc.gridx = 1;
		gbc.gridy = row;
	    gbc.weightx = 0.6;
		this.add(this.txtUser, gbc);
		
		row++;
		
		gbc.gridx = 0;
		gbc.gridy = row;
		gbc.weightx = 0.4;
		this.add(new JLabel("Password"), gbc);
		
		this.txtPass = new JTextField();
		gbc.gridx = 1;
		gbc.gridy = row;
	    gbc.weightx = 0.6;
		this.add(this.txtPass, gbc);
		
		row++;
		
		this.btnConnect = new JButton("Connect");
		this.btnConnect.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				if (dbConfig.connType == DBTypeEnum.MYSQL) {
					try {
						AccessMySQL dba = AccessMySQL.getInstance();
						DSN dsn = getInputDSN();
						dba.setDSN(dsn);
						dba.connect("");
						String[] dbList = dba.getDataBasesList();
						dbConfig.cboDatabase.removeAllItems();
						for (String dbName : dbList) {
							dbConfig.cboDatabase.addItem(dbName);
						}
						dba.closeConnection();
						//dbConfig.cboDatabases.setEditable(false);
						dbConfig.cboDatabase.setEnabled(true);
					} catch (Exception e) {
						JOptionPane.showMessageDialog(null, e.getMessage(),
								"Connection error", JOptionPane.WARNING_MESSAGE);
					}
				}
				else {
					// TODO: ???
				}
			}
		});
		gbc.gridx = 0;
		gbc.gridy = row;
	    gbc.gridwidth = 2;
		gbc.fill = GridBagConstraints.NONE;
	    gbc.weightx = 1;
		this.add(this.btnConnect, gbc);

		row++;
		
		gbc.gridx = 0;
		gbc.gridy = row;
	    gbc.gridwidth = 1;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.weightx = 0.4;
		this.add(new JLabel("Database"), gbc);
		
		this.cboDatabase = new JComboBox<String>();
		this.cboDatabase.setEditable(true);
		gbc.gridx = 1;
		gbc.gridy = row;
	    gbc.weightx = 0.6;
		this.add(this.cboDatabase, gbc);
		
		row++;
		
		JButton btnOK = new JButton("OK");
		btnOK.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				DSN dsn = getInputDSN();
				dbConfig.configProps.setProperty("host", dsn.host);
				dbConfig.configProps.setProperty("port", dsn.port);
				dbConfig.configProps.setProperty("user", dsn.user);
				dbConfig.configProps.setProperty("pass", dsn.pass);
				dbConfig.configProps.setProperty("type", dbConfig.connType.toString());
				String dbName = dbConfig.cboDatabase.getEditor().getItem().toString().trim();
				dbConfig.configProps.setProperty("dbname", dbName);
				if (dbConfig.isConfigOK(true)) {
					/* hide dialog if config is ok */
					dbConfig.setVisible(false);
					Debug.print("entered configuration:\n" + 
							DSNHelper.getPropertiesInfo(dbConfig.configProps));
				}
			}
		});
		gbc.gridx = 0;
		gbc.gridy = row;
		gbc.fill = GridBagConstraints.NONE;
		gbc.weightx = 0.4;
		this.add(btnOK, gbc);
		
		JButton btnCancel = new JButton("Cancel");
		btnCancel.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				/* just hide dialog */
				dbConfig.setVisible(false);
			}
		});
		gbc.gridx = 1;
		gbc.gridy = row;
	    gbc.weightx = 0.6;
		this.add(btnCancel, gbc);
		
		this.cboHost.getEditor().setItem(this.configProps.getProperty("host"));
		//new ActionEvent(this.cboHost, ActionEvent.ACTION_PERFORMED, null);	// doesn't work?
		this.connType = this.getConnTypeFromHost(this.configProps.getProperty("host"));
		this.handleHostInput();
		this.txtPort.setText(this.configProps.getProperty("port"));
		this.txtUser.setText(this.configProps.getProperty("user"));
		this.txtPass.setText(this.configProps.getProperty("pass"));
		
		this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		this.pack();
		this.setVisible(true);
		
		while (this.isShowing()) {
			try {
				Thread.sleep(100);
			} catch (Exception e) {
				throw new DBException("Error while sleeping", e);
			}
		}
		Debug.print("hide dialog");
	}
	/**
	 * Get the entered values as DSN.
	 * @return
	 */
	private DSN getInputDSN() {
		String host = this.getHostName();
		String port = this.txtPort.getText().trim();
		String user = this.txtUser.getText().trim();
		String pass = this.txtPass.getText().trim();
		return new DSN(host, port, user, pass);
	}
	/**
	 * Select the corresponding item in combo box related to the
	 * connection type (gotten from entered host name).
	 */
	private void handleHostInput() {
		String hostName = this.getHostName();
		this.connType = this.getConnTypeFromHost(hostName);
		if (this.connType == DBTypeEnum.ACCESS) {
			dbConfig.cboHost.setSelectedItem(new HostName(DBTypeEnum.ACCESS, "Access"));
		}
		else if (this.connType == DBTypeEnum.DERBY) {
			dbConfig.cboHost.setSelectedItem(new HostName(DBTypeEnum.DERBY, "Derby"));
		}
		else {
			dbConfig.cboHost.setSelectedItem(new HostName(DBTypeEnum.MYSQL, hostName));
		}
	}
	/**
	 * Get entered host name from combo box.
	 * @return String
	 */
	private String getHostName() {
		return this.cboHost.getEditor().getItem().toString().trim();
	}
	/**
	 * Get associated connection type from the given host name.
	 * @param hostName
	 * @return DBTypeEnum
	 */
	private DBTypeEnum getConnTypeFromHost(String hostName) {
		DBTypeEnum connType = DBTypeEnum.MYSQL;
		if (hostName.equalsIgnoreCase("Access")) {
			connType = DBTypeEnum.ACCESS;
		}
		else if (hostName.equalsIgnoreCase("Derby")) {
			connType = DBTypeEnum.DERBY;
		}
		return connType;
	}
}
